package com.unibuy.smartdevice.devices;

import android.content.Context;
import android.serialport.SerialPort;
import android.util.Log;

import com.unibuy.smartdevice.exception.Logs;
import com.unibuy.smartdevice.exception.LogsIOException;
import com.unibuy.smartdevice.exception.LogsSecurityException;
import com.unibuy.smartdevice.exception.LogsUnsupportedOperationException;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

public class Rs232Dev {
    private Logs logs;
    private boolean showLogs = false;
    private static List<String> rs232DeviceList;
    private SerialPort serialPort;
    private boolean isRead = false;
    public void setRead(boolean read) {
        isRead = read;
    }
    public boolean isRead() {
        return isRead;
    }

    public static String searchSuPath() throws LogsSecurityException {
        File fileSu = new File("/system/bin/su_root");

        if (!fileSu.exists()) {
            fileSu = new File("/system/bin/su");
            if (!fileSu.exists()) {
                fileSu = new File("/system/xbin/su");
            }
        }

        if (fileSu.exists()) {
            return fileSu.getAbsolutePath();
        } else {
            throw new LogsSecurityException(new Logs(Rs232Dev.class), "su file not found");
        }
    }

    private static String suPath;

    public static void setSuPath(Context context) {
        try {
            suPath = searchSuPath();
            SerialPort.setSuPath(suPath);
        } catch (LogsSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    public Rs232Dev() {
        this.logs = new Logs(this.getClass());
        initDeviceList();
    }

    public Rs232Dev(ComPort comPort) throws LogsUnsupportedOperationException, LogsIOException {
        this();
        connectPort(comPort);
    }

    private void initDeviceList() {
        if (rs232DeviceList == null) rs232DeviceList = new ArrayList<>();
        if (rs232DeviceList.isEmpty()) {
            for (ComPort comPort: ComPort.values()) {
                if (chmod666File(comPort.getFilepath())) {
                    rs232DeviceList.add(comPort.getFilepath());
                }
            }
        }
    }

    private boolean chmod666File(String comPortPath) {
        if (showLogs) this.logs.info("chmod666File getDevice:" + comPortPath);

        File fileComPort = new File(comPortPath);

        if (!fileComPort.exists()) {
            if (showLogs) this.logs.info(fileComPort.getAbsolutePath() + " not exists:" + fileComPort.exists());

            return false;
        }

        if (showLogs) this.logs.info(fileComPort.getAbsolutePath() + " exists:true canRead:" + fileComPort.canRead() + " canWrite:" + fileComPort.canWrite());

        if (fileComPort.canRead() && fileComPort.canWrite()) {
            return true;
        }

        try {
            if (showLogs) this.logs.info("suPath:" + suPath);

            Process su = Runtime.getRuntime().exec(suPath);
            String cmd = "chmod 666 " + fileComPort.getAbsolutePath() + "\nexit\n";
            su.getOutputStream().write(cmd.getBytes());
            su.getOutputStream().flush();

            // 使用執行緒處理標準輸出和錯誤輸出
            Thread stdoutThread = new Thread(() -> readStream(su.getInputStream(), showLogs, "OUTPUT"));
            Thread stderrThread = new Thread(() -> readStream(su.getErrorStream(), showLogs, "ERROR"));

            stdoutThread.start();
            stderrThread.start();

            // 等待子程序完成
            int exitCode = su.waitFor();

            // 等待輸出執行緒結束
            stdoutThread.join();
            stderrThread.join(); // 確保執行緒執行完畢

            if (su.waitFor() != 0) {
                return false;
            }

            if (showLogs) this.logs.info(fileComPort.getAbsolutePath() + " set canRead:" + fileComPort.canRead() + " canWrite:" + fileComPort.canWrite());

            if (fileComPort.canRead() && fileComPort.canWrite()) {
                return true;
            } else {
                return false;
            }
        } catch (IOException | InterruptedException e) {
            this.logs.info(fileComPort.getAbsolutePath() + " error");
            return false;
        }
    }

    private void readStream(InputStream inputStream, boolean showLogs, String streamType) {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (showLogs) {
                    logs.info(streamType + ": " + line);
                }
            }
        } catch (IOException e) {
            logs.warning(e);
        }
    }

    public void connectPort(ComPort comPort) throws LogsUnsupportedOperationException, LogsIOException {
        // 校验位；0:无校验位(NONE，默认)；1:奇校验位(ODD);2:偶校验位(EVEN)
        // Check bit; 0: no check bit (NONE, default); 1: odd check bit (ODD); 2: even check bit (EVEN)
        //    .parity(2)
        // 数据位,默认8；可选值为5~8
        // Data bit, default 8; optional value is 5~8
        //    .dataBits(7)
        // 停止位，默认1；1:1位停止位；2:2位停止位
        // Stop bit, default 1; 1:1 stop bit; 2: 2 stop bit
        //    .stopBits(2)

        try {
            if (rs232DeviceList.contains(comPort.getFilepath())) {
//                closePort();
                this.serialPort = SerialPort
                        .newBuilder(comPort.getFilepath(), comPort.getBaudRate())
                        .build();
                if (showLogs) Log.i("connect", "file:" + comPort.getFilepath() + " baudrate:" + comPort.getBaudRate());
            } else {
                throw new LogsIOException(logs, "Serialport not found:" + comPort.getFilepath());
            }
        } catch (IOException e) {
            throw new LogsIOException(logs, e.getLocalizedMessage());
        }
    }

    public byte[] readToMatch(byte stx1, byte stx2, int moveLimit) throws LogsIOException {
        return readToMatch(new byte[]{stx1, stx2}, moveLimit);
    }

    public byte[] readToMatch(byte stx1, int moveLimit) throws LogsIOException {
        return readToMatch(new byte[]{stx1}, moveLimit);
    }

    public byte[] readToMatch(byte[] stx, int moveLimit) throws LogsIOException {
        byte[] buffer = new byte[0];
        try {
            InputStream inputStream = this.serialPort.getInputStream();
            int moveCount = 0;
            while (moveCount <= moveLimit || moveLimit == 0) {
                byte[] tmp = new byte[1];
                if (inputStream.read(tmp) >= 1) {
                    if (PortTools.match(tmp[0], stx)) {
                        buffer = PortTools.concatByteArrays(buffer, tmp);
                        return buffer;
                    } else {
                        buffer = PortTools.concatByteArrays(buffer, tmp);
                    }
                }
                moveCount++;
            }
            return buffer;
        } catch (IOException e) {
            throw new LogsIOException(logs, e.getLocalizedMessage());
        }
    }

    public byte[] readListen(int readMax, long delayMillis) throws LogsIOException {
        InputStream inputStream = this.serialPort.getInputStream();
        byte[] buffer = new byte[0];
        int readCount = 0;
        while (true) {
            try {
                int size = inputStream.available();
//                logs.info("size:" + size);
                if (size > 0) {
                    byte[] tmp = new byte[size];
                    inputStream.read(tmp);
                    buffer = PortTools.concatByteArrays(buffer, tmp);
                } else {
                    Thread.sleep(delayMillis);
                }
            } catch (IOException e) {
                throw new LogsIOException(logs, "readListen:" + e.getMessage());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            readCount++;
            if (readCount >= readMax) break;
        }

        if (showLogs) logs.debug("read buffer:"+PortTools.showHex(buffer));
        return buffer;
    }

    public int read(byte[] buffer) throws LogsIOException {
        try {
            InputStream inputStream = this.serialPort.getInputStream();
            int size = inputStream.read(buffer);
            if (showLogs) logs.debug("read buffer:"+PortTools.showHex(buffer));
            return size;
        } catch (IOException e) {
            throw new LogsIOException(logs, "read:" + e.getMessage());
        }
    }

    public void send(byte[] buffer) throws LogsIOException {
        try {
            OutputStream outputStream = this.serialPort.getOutputStream();
            outputStream.write(buffer);
            outputStream.flush();
            if (showLogs) logs.debug("send buffer:"+PortTools.showHex(buffer));
        } catch (IOException e) {
            throw new LogsIOException(logs, "send:" + e.getMessage());
        }
    }
    public int transmission(byte[] sendBuffer, byte[] readBuffer) throws LogsIOException {
        try {
            OutputStream outputStream = this.serialPort.getOutputStream();
            InputStream inputStream = this.serialPort.getInputStream();
            outputStream.write(sendBuffer);
            outputStream.flush();
            if (showLogs) logs.debug("send buffer:"+PortTools.showHex(sendBuffer));
            int size = inputStream.read(readBuffer);
            if (showLogs) logs.debug("read buffer:"+PortTools.showHex(readBuffer));
            return size;
        } catch (IOException e) {
            throw new LogsIOException(logs, e.getLocalizedMessage());
        }
    }

    public void closePort() throws LogsIOException {
            if (this.serialPort != null) {
//                OutputStream outputStream = this.serialPort.getOutputStream();
//                if (outputStream != null) {
//                    outputStream.close();
//                    outputStream = null;
//                }
//
//                InputStream inputStream = this.serialPort.getInputStream();
//                if (inputStream != null) {
//                    inputStream.close();
//                    inputStream = null;
//                }

                SerialPort serialPort = this.serialPort;
                if (serialPort != null) {
                    serialPort.close();
                }
            }
    }

    public static void getDeviceInfo() {
        System.out.println("----- 偵測Su檔 -----");
        try {
            System.out.println(searchSuPath());
        } catch (LogsSecurityException e) {
            System.out.println("找不到Su檔案");
        }

        System.out.println("----- 遍巡tty檔 -----");
        File devDir = new File("/dev");
        if (devDir.exists() && devDir.isDirectory()) {
            String[] files = devDir.list();
            if (files != null) {
                for (String file : files) {
                    if (file.contains("tty")) {
                        File ttyFile = new File("/dev/"+file);
                        System.out.println(ttyFile.getAbsolutePath() + " exists:true canRead:" + ttyFile.canRead() + " canWrite:" + ttyFile.canWrite());
                    }
                }
            } else {
                System.out.println("無法列出 /dev 目錄內容。");
            }
        } else {
            System.out.println("/dev 目錄不存在或無法訪問。");
        }
    }
}
